package semant;

import semant.firstpass.ProgramHandler;
import symbol.ClassInfo;
import symbol.MethodInfo;
import symbol.Symbol;
import symbol.VarInfo;
import syntaxtree.Identifier;
import syntaxtree.IdentifierType;
import syntaxtree.Program;
import syntaxtree.Type;
import errors.ErrorEchoer;

public class TypeChecker
{
    private TypeChecker()
    {
        super();
    }

    public static Env TypeCheck(ErrorEchoer err, Program p)
    {		
    	Env e = firstPass(err, p);
    	secondPass(e, p);
    	
        return e;
    }
    
    private static Env firstPass(ErrorEchoer err, Program p)
    {
    	return ProgramHandler.firstPass(err, p);
    }
    
    private static void secondPass(Env e, Program p)
    {
    	semant.secondpass.ProgramHandler.secondPass(e, p);
    }
    
    /**
     * Obtêm variáveis visíveis no escopo do método e classe informados.
     * @param ci
     * @param mi
     * @param name
     * @return VarInfo da variável relacionada ou NULL caso variável não esteja visível (i.e. não declarada)
     */
    public static VarInfo getVariable(ClassInfo ci, MethodInfo mi, String name)
    {
    	VarInfo vi;    
    	Symbol s = Symbol.symbol(name);
    	
    	if ((vi = mi.formalsTable.get(s)) != null)
    		return vi;
    	
    	if ((vi = mi.localsTable.get(s)) != null)
    		return vi;
    	
    	if ((vi = ci.attributes.get(s)) != null)
    		return vi;
    	
    	return null;
    }
    
    /**
     * Adiciona erro para identificador não declarado.
     * @param e
     * @param id
     */
    public static void erroIdentificadorNaoDeclarado(Env e, Identifier id)
    {
    	e.err.Error(id, new Object[]{"Identificador não declarado.",
    			"Símbolo: " + id.s});
    }
    
    /**
     * Compara dois tipos e retorna true se forem iguais (ou derivados).
     * Esse método responde a perguntar: B é do tipo A?
     * @param a
     * @param b
     * @return
     */
    public static boolean compareType(Type a, Type b, Env e)
    {
    	// ou ambos tipos sao objetos de classes do usuario
    	if (a instanceof IdentifierType && b instanceof IdentifierType)
    	{
    		Symbol as;
    		ClassInfo ci;
    		
    		as = Symbol.symbol(((IdentifierType)a).name);  		
    		ci = e.classes.get(Symbol.symbol(((IdentifierType)b).name));

    		while (ci != null)
    		{    		
	    		if (as == ci.name)
	    			return true;
	    		
	    		ci = ci.base;
    		}
    	}
      	// ou sao tipos definidos da linguagem
    	else if (a.getClass() == b.getClass())
    		return true;

    	return false;
    }
}
